#
# Package for handling the actual solving of matrices
# Many of the functions don't return any values because they work directly on the matrix object
#
# 2009, Mike Widner <mikewidner@gmail.com>
#
################

package Fangcheng::Calc::Determinants;

use 5.006001;
use strict;
use warnings;
use vars qw(@ISA);
use Carp;
use Fangcheng::Step;

require Fangcheng::Calc;
@ISA = qw( Fangcheng::Calc );

my $matrixStep = Fangcheng::Step->new();

# needs to store db object to make it easier for handling steps storage, etc.
# so override base class new()
sub new {
	my ($class, $matrix) = @_;
	my $self = {};
	bless $self, ref($class) || $class;
	return( $self );
}

# solve a matrix by the determinantal method
sub solve {
	my ($self, $matrix) = @_;
	
	$matrixStep->step(0);	
	$matrixStep->save( $matrix );
	
	$self->nextStage( $matrixStep->step );
	$self->setNegatives( $matrix );
	
	$self->nextStage( $matrixStep->step );
	$self->calculateDividend( $matrix );
	
	$self->nextStage( $matrixStep->step );
	$self->calculateDivisor( $matrix );
	$self->nextStage( $matrixStep->step );
	
	return( $matrixStep );
}

sub setNegatives {
	my ($self, $matrix) = @_;
	
	my $lastRow = $matrix->rows();
	my $lastCol = $matrix->columns();

	$self->convertFractionsToIntegers( $matrix );

	my $val = $matrix->get( $lastRow, 1 );

	foreach my $i ( 1 .. $lastRow - 2 ) {
		$matrix = $matrixStep->next();
		$val *= $matrix->get( $i, $i + 1 );
		$matrix->set( $lastRow, $i + 1, $val * $self->plusMinus( $i ) );
	}
}

sub calculateDividend {
	my ($self, $matrix) = @_;

	my $lastRow = $matrix->rows();
	my $lastCol = $matrix->columns();
				
	foreach my $i ( 1 .. $lastRow - 1) {		
		$matrix = $matrixStep->next();
		my $prev = $matrixStep->prev();
		my $val = ( $prev->get( $lastRow, $lastCol ) * $matrix->get( $i, $i ) )
				- ( $matrix->get( $lastRow, $i ) 	 * $matrix->get( $i, $lastCol ) );
		$matrix->set( $lastRow, $lastCol, $val );
	}	
}

# doesn't factor into calculations, figures for display only as of now
sub calculateDivisor {
	my ($self, $matrix) = @_;
	
	$matrix = $matrixStep->next();
	my $lastRow = $matrix->rows();
	my $lastCol = $matrix->columns();
	
	my $div = 1;
	foreach my $i ( 1 .. $matrix->rows() ) {
		$div *= $matrix->get( $i, $i );
	}
	my $first = $matrixStep->first();
	my $div2 = $first->get( $lastRow, 1 );
	foreach my $i ( 1 .. $first->rows() - 1 ) {
		$div2 *= $first->get( $i, $i + 1);
	}
	$div += $div2;
	
	my $val = $matrix->get($lastRow, $lastCol);
	return if ( $div == 0 );	# let's not divide by zero
	if ($val % $div != 0) {
		$matrix->set($lastRow, $lastCol, $self->simplifyFraction( "$val / $div" ) );	
	}
	else {
		$matrix->set($lastRow, $lastCol, $val / $div);
	}
}

# returns the total number of steps needed to solve a given matrix
sub totalSteps {
	my ($self, $matrix) = @_;
	return( 2 * $matrix->rows() - 2 );
}

# returns 1 if even, negative 1 if odd
sub plusMinus { 
	my ($self, $val) = @_;
	my $sign = 1;
	if ( $val % 2 != 0 ) {
		$sign = -1;
	} 
	return( $sign );
}

# returns 1 if the given field can be non-zero and we can still solve it with determinanal solution
sub canSetField {
	my ($self, $matrix, $row, $col) = @_;
	
	# yeah, this is really ugly
	return( 1 ) if $col == $matrix->columns;
	return( 1 ) if ( $row == $col or $row == $col - 1 );
	return( 1 ) if ( $row == $matrix->rows and $col == 1 );
	return( 0 );
}

1;